package com.acxy.ants.core.plugin.sqlmap;

import com.alibaba.fastjson.JSON;
import ognl.OgnlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;

import java.net.BindException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author MrShun
 * @version 1.0
 * @Date 2017-05-20
 */
public class SqlXmlParser {

    private static final Logger logger = LoggerFactory.getLogger(SqlXmlParser.class);


    protected static Map<String, List<SqlNode>> sqlMap = new ConcurrentHashMap<>();

    private static final String staticStartSymbol = "#{";

    private static final String staticEnndSymbol = "}";

    private static final String dynamicStartSymbol = "${";

    private static final String dynamicEndSymbol = "}";

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 将dom文件sql解析出来以键值对形式放入链表
     *
     * @param document
     */
    public static void parse(Document document) {
        Element documentElement = document.getDocumentElement();
        if ("mapper".equals(documentElement.getTagName())) {
            String rootName = documentElement.getAttribute("namespace");
            if (rootName.isEmpty()) throw new RuntimeException("the namespace domain must be defined!");
            NodeList nodeList = documentElement.getElementsByTagName("sql");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node item = nodeList.item(i);
                String key = rootName + "." + ((Element) item).getAttribute("id");
                NodeList childs = item.getChildNodes();
                List<SqlNode> nodes = new ArrayList<>();
                for (int j = 0; j < childs.getLength(); j++) {
                    Node it = childs.item(j);
                    if (it.getNodeType() == Node.TEXT_NODE) {
                        nodes.add(new TextSqlNode(it.getTextContent()));
                    } else {
                        String nodeName = it.getNodeName();
                        if (nodeName == Tag.INCLUDE) {
                            String refid = ((Element) it).getAttribute("refid");
                            String includKey = rootName + "." + refid;
                            List<SqlNode> sqlNodes = sqlMap.get(includKey);
                            if (sqlNodes == null || sqlNodes.size() == 0)
                                throw new RuntimeException(includKey + " the includ reference node must exist or can only be configured in the front!");
                            nodes.add(new IncludeSqlNode(sqlNodes.get(0)));
                        } else if (nodeName == Tag.IF) {
                            nodes.add(new IfSqlNode(it));
                        } else if (nodeName == Tag.WHERE) {
                            nodes.add(new WhereSqlNode(it));
                        } else if (nodeName == Tag.SET) {
                            nodes.add(new SetSqlNode(it));
                        } else if (nodeName == Tag.TRIM) {
                            nodes.add(new TrimSqlNode(it));
                        } else if (nodeName == Tag.CHOOSE) {
                            nodes.add(new ChooseSqlNode(it));
                        } else if (nodeName == Tag.FOREACH) {
                            nodes.add(new ForEachSqlNode(it));
                        }
                    }
                }
                sqlMap.put(key, nodes);

            }

        }
    }


    /**
     * 获取完整的SQL语句
     *
     * @param key   查询key
     * @param param 参数
     * @return
     */
    public static String getBoundSql(String key, Object param) {
        List<SqlNode> sqlNodes = sqlMap.get(key);
        if (sqlNodes == null || sqlNodes.size() == 0) throw new IllegalArgumentException("not find " + key + "!");
        StringBuffer sb = new StringBuffer();
        for (SqlNode sqlNode : sqlNodes)
            sb.append(sqlNode.getResult(param));
        String sql = sb.toString().replaceAll("\r|\n|\t|", "").replaceAll(" +", " ").trim();
        if (param == null) return sql;
        return parseSql(key, sql, param);
    }

    public static String getBoundSql(String key) {
        return getBoundSql(key, null);
    }

    /**
     * 根据命名空间和参数对象获取sql语句对象
     * @param key 命名空间
     * @param params 参数对象
     * @return
     */
    public static SqlParams getPreparedStatement(String key, Object params) {
        List<SqlNode> sqlNodes = sqlMap.get(key);
        if (sqlNodes == null || sqlNodes.size() == 0) throw new IllegalArgumentException("not find " + key + "!");
        StringBuffer sb = new StringBuffer();
        for (SqlNode sqlNode : sqlNodes)
            sb.append(sqlNode.getResult(params));
        String sql = sb.toString().replaceAll("\r|\n|\t|", "").replaceAll(" +", " ").trim();
        if(params == null) return new SqlParams(sql, null);
        List<Object> values = new ArrayList<>();
        if (params instanceof Map) {
            Set<Map.Entry<String, Object>> entries = ((Map<String, Object>) params).entrySet();
            for (Map.Entry<String, Object> entry : entries) {
                String ikey = "#{" + entry.getKey() + "}";
                Object v = entry.getValue();
                if (sql.indexOf(ikey)!=-1) {
                    values.add(v);
                    sql = sql.replace(ikey, "? ");
                }
                String dkey = "${" + entry.getKey() + "}";
                if (sql.indexOf(dkey)!=-1) {
                    values.add(entry.getValue());
                    sql = sql.replace(dkey, String.valueOf(v));
                }
            }
        }else{
            throw new IllegalArgumentException("["+params + "] 传入的数据对象必须Map类型!");
        }
        return new SqlParams(sql, values.toArray());
    }
    public static SqlParams getPreparedStatement(String key){
        return getPreparedStatement(key, null);
    }
    /**
     * 解析sql并且填充参数
     *
     * @param statement 查询sql的key
     * @param sql       未解析sql
     * @param param     参数
     * @return
     */
    private static String parseSql(String statement, String sql, Object param) {
        //循环处理数据值参数
        while (sql.indexOf(staticStartSymbol) != -1 && sql.indexOf(staticEnndSymbol) != -1) {
            int start = sql.indexOf(staticStartSymbol);
            int end = sql.indexOf(staticEnndSymbol, start);
            String key = sql.substring(start + staticStartSymbol.length(), end);
            try {
                Object value = OgnlCache.getValue(key, param);
                if (value != null) {
                    Class<?> cls = value.getClass();
                    String tagValue = staticStartSymbol + key + staticEnndSymbol;
                    if (cls == String.class || cls == char.class)
                        sql = sql.replace(tagValue, "'" + value + "'");
                    else if (cls == Date.class) {
                        sql = sql.replace(tagValue, "'" + sdf.format(value) + "'");
                    } else
                        sql = sql.replace(tagValue, String.valueOf(value));
                } else {
                    throw new IllegalArgumentException(statement + " > parameter '" + key + "' not found!");
                }
            } catch (Exception ee) {
                throw new IllegalArgumentException(statement + " > parameter '" + key + "' not found!");
            }
        }

        //循环处理注入变量并且安全过滤
        while (sql.indexOf(dynamicStartSymbol) != -1 && sql.indexOf(dynamicEndSymbol) != -1) {
            int start = sql.indexOf(dynamicStartSymbol);
            int end = sql.indexOf(dynamicEndSymbol, start);
            String key = sql.substring(start + dynamicStartSymbol.length(), end);
            try {
                Object value = OgnlCache.getValue(key, param);
                String v = String.valueOf(value);
                if (v != null) {
                    String tagValue = dynamicStartSymbol + key + dynamicEndSymbol;
                    if (v.indexOf(" ") != -1 && (v.toUpperCase().indexOf("AND") != -1 || v.toUpperCase().indexOf("OR") != -1))
                        logger.info(tagValue + "|" + v + " discovery of injection risk!");
                    sql = sql.replace(tagValue, v.replace(" ", ""));
                } else {
                    throw new IllegalArgumentException(statement + " > parameter '" + key + "' not found!");
                }
            } catch (Exception e) {
                throw new IllegalArgumentException(statement + " > parameter '" + key + "' not found!");
            }
        }
        return sql;
    }

    public static void clear() {
        sqlMap.clear();
    }

}
